Node.js SDK 使用指南

SDK 下载地址:https://github.com/qiniu/nodejs-sdk

文档大纲

概述

该 SDK 适用于 Node.js 0.4.7 及其以上版本,基于 七牛云存储官方API 构建。 若您的服务端是一个基于 Node.js 编写的网络程序,使用此 SDK , 能让您以非常便捷地方式将数据安全地存储到七牛云存储上。 以便让您应用的终端用户进行高速上传和下载,同时也使得您的服务端更加轻盈。

Node.js SDK 主要包含对七牛云存储API的包装,遵循qiniu sdkspec 涉及到以下几个方面:

  • 服务端操作,生成上传授权(uptoken),私有bucket下载URL(downloadUrl),文件操作授权
  • 客户端操作,上传文件(qiniu/io.js)
  • 文件管理(qiniu/rs.js)
  • 数据处理(qiniu/fop.js)
  • 公共库(qiniu/rpc.js, qiniu/util.js)

准备开发环境

环境依赖

适用于 Node.js 0.4.7 及其以上版本

安装

通过 npm 以 node 模块化的方式安装:
npm install qiniu

ACCESS_KEY 和 SECRET_KEY

在使用SDK 前,您需要拥有一对有效的 AccessKey 和 SecretKey 用来进行签名授权。

可以通过如下步骤获得:

  1. 开通七牛开发者帐号
  2. 登录七牛开发者自助平台,查看 AccessKey 和 SecretKey

使用SDK

初始化环境

对于服务端而言,常规程序流程是:

qiniu.conf.ACCESS_KEY = '<Your Access Key>'
qiniu.conf.SECRET_KEY = '<Your Secret Key>'

服务端操作时请务必初始化这两个变量

上传文件

为了尽可能地改善终端用户的上传体验,七牛云存储首创了客户端直传功能。一般云存储的上传流程是:

客户端(终端用户) => 业务服务器 => 云存储服务

这样多了一次上传的流程,和本地存储相比,会相对慢一些。但七牛引入了客户端直传,将整个上传过程调整为:

客户端(终端用户) => 七牛 => 业务服务器

客户端(终端用户)直接上传到七牛的服务器,通过DNS智能解析,七牛会选择到离终端用户最近的ISP服务商节点,速度会比本地存储快很多。文件上传成功以后,七牛的服务器使用回调功能,只需要将非常少的数据(比如Key)传给应用服务器,应用服务器进行保存即可。

上传流程

在七牛云存储中,整个上传流程大体分为这样几步:

  1. 业务服务器颁发 上传凭证给客户端(终端用户)
  2. 客户端凭借 上传凭证 上传文件到七牛
  3. 在七牛获得完整数据后,发起一个 HTTP 请求回调到业务服务器
  4. 业务服务器保存相关信息,并返回一些信息给七牛
  5. 七牛原封不动地将这些信息转发给客户端(终端用户)

需要注意的是,回调到业务服务器的过程是可选的,它取决于业务服务器颁发的 上传凭证。如果没有回调,七牛会返回一些标准的信息(比如文件的 hash)给客户端。如果上传发生在业务服务器,以上流程可以自然简化为:

  1. 业务服务器生成 uptoken(不设置回调,自己回调到自己这里没有意义)
  2. 凭借 上传凭证 上传文件到七牛
  3. 善后工作,比如保存相关的一些信息

上传策略

上传凭证 实际上是用 AccessKey/SecretKey 进行数字签名的上传策略(rs.PutPolicy),它控制着整个上传流程的行为。让我们快速过一遍你都能够决策啥:

function PutPolicy(scope, callbackUrl, callbackBody, returnUrl, returnBody,
                  asyncOps, endUser, expires) {
  this.scope = scope || null;
  this.callbackUrl = callbackUrl || null;
  this.callbackBody = callbackBody || null;
  this.returnUrl = returnUrl || null;
  this.returnBody = returnBody || null;
  this.asyncOps = asyncOps || null;
  this.endUser = endUser || null;
  this.expires = expires || 3600;
}
  • scope 限定客户端的权限。如果 scope 是 bucket,则客户端只能新增文件到指定的 bucket,不能修改文件。如果 scope 为 bucket:key,则客户端可以修改指定的文件。注意: key必须采用utf8编码,如使用非utf8编码访问七牛云存储将反馈错误
  • callbackUrl 设定业务服务器的回调地址,这样业务服务器才能感知到上传行为的发生。
  • callbackBody 设定业务服务器的回调信息。文件上传成功后,七牛向业务服务器的callbackUrl发送的POST请求携带的数据。支持 魔法变量自定义变量
  • returnUrl 设置用于浏览器端文件上传成功后,浏览器执行303跳转的URL,一般为 HTML Form 上传时使用。文件上传成功后浏览器会自动跳转到 returnUrl?upload_ret=returnBody
  • returnBody 可调整返回给客户端的数据包,支持 魔法变量自定义变量returnBody 只在没有 callbackUrl 时有效(否则直接返回 callbackUrl 返回的结果)。不同情形下默认返回的 returnBody 并不相同。在一般情况下返回的是文件内容的 hash,也就是下载该文件时的 etag;但指定 returnUrl 时默认的 returnBody 会带上更多的信息。
  • asyncOps 可指定上传完成后,需要自动执行哪些数据处理。这是因为有些数据处理操作(比如音视频转码)比较慢,如果不进行预转可能第一次访问的时候效果不理想,预转可以很大程度改善这一点。
  • expires指定uptoken的过期时间,默认3600s

关于上传策略更完整的说明,请参考 上传凭证

生成上传凭证

服务端生成 上传凭证 代码如下:

function uptoken(bucketname) {
  var putPolicy = new qiniu.rs.PutPolicy(bucketname);
  //putPolicy.callbackUrl = callbackUrl;
  //putPolicy.callbackBody = callbackBody;
  //putPolicy.returnUrl = returnUrl;
  //putPolicy.returnBody = returnBody;
  //putPolicy.asyncOps = asyncOps;
  //putPolicy.expires = expires;

  return putPolicy.token();
}

PutExtra

PutExtra是上传时的可选信息,默认为null

function PutExtra(params, mimeType, crc32, checkCrc) {
  this.paras = params || {};
  this.mimeType = mimeType || null;
  this.crc32 = crc32 || null;
  this.checkCrc = checkCrc || 0;
}
  • params 是一个字典。自定义变量,key必须以 x: 开头命名,不限个数。可以在 uploadToken 的 callbackBody 选项中求值。
  • mime_type 表示数据的MimeType,当不指定时七牛服务器会自动检测。
  • crc32 待检查的crc32值
  • check_crc 可选值为0, 1, 2。 check_crc == 0: 表示不进行 crc32 校验。 check_crc == 1: 上传二进制数据时等同于 check_crc=2;上传本地文件时会自动计算 crc32 值。 check_crc == 2: 表示进行 crc32 校验,且 crc32 值就是上面的 crc32 变量
上传文件

上传文件到七牛(通常是客户端完成,但也可以发生在服务端):

直接上传二进制流:

function uploadBuf(body, key, uptoken) {
  var extra = new qiniu.io.PutExtra();
  //extra.params = params;
  //extra.mimeType = mimeType;
  //extra.crc32 = crc32;
  //extra.checkCrc = checkCrc;

  qiniu.io.put(uptoken, key, body, extra, function(err, ret) {
    if (!err) {
      // 上传成功, 处理返回值
      console.log(ret.key, ret.hash);
      // ret.key & ret.hash
    } else {
      // 上传失败, 处理返回代码
      console.log(err)
      // http://developer.qiniu.com/docs/v6/api/reference/codes.html
    }
  });
}

上传本地文件:

function uploadFile(localFile, key, uptoken) {
  var extra = new qiniu.io.PutExtra();
  //extra.params = params;
  //extra.mimeType = mimeType;
  //extra.crc32 = crc32;
  //extra.checkCrc = checkCrc;

  qiniu.io.putFile(uptoken, key, localFile, extra, function(err, ret) {
    if(!err) {
      // 上传成功, 处理返回值
      console.log(ret.key, ret.hash);
      // ret.key & ret.hash
    } else {
      // 上传失败, 处理返回代码
      console.log(err);
      // http://developer.qiniu.com/docs/v6/api/reference/codes.html
    }
  });
}

下载文件

下载公有文件

每个 bucket 都会绑定一个或多个域名(domain)。如果这个 bucket 是公开的,那么该 bucket 中的所有文件可以通过一个公开的下载 url 可以访问到:

http://<domain>/<key>

其中是bucket所对应的域名。七牛云存储为每一个bucket提供一个默认域名。默认域名可以到七牛云存储开发者平台中,空间设置的域名设置一节查询。用户也可以将自有的域名绑定到bucket上,通过自有域名访问七牛云存储。

假设某个 bucket 既绑定了七牛的二级域名,如 hello.qiniudn.com,也绑定了自定义域名(需要备案),如www.hello.com。而该 bucket 中 有一个key 为 a/b/c.htm 的文件可以通过http://hello.qiniudn.com/a/b/c.htmhttp://www.hello.com/a/b/c.htm中任意一个 url 进行访问。

注意: key必须采用utf8编码,如使用非utf8编码访问七牛云存储将反馈错误

下载私有文件

如果某个 bucket 是私有的,那么这个 bucket 中的所有文件只能通过一个的临时有效的 downloadUrl 访问:

http://<domain>/<key>?e=<deadline>&token=<dntoken>

其中 dntoken 是由业务服务器签发的一个临时下载授权凭证,deadline 是 dntoken 的有效期。dntoken不需要单独生成,SDK 提供了生成完整 downloadUrl 的方法(包含了 dntoken),示例代码如下:

function downloadUrl(domain, key) {
  var baseUrl = qiniu.rs.makeBaseUrl(domain, key);
  var policy = new qiniu.rs.GetPolicy();
  return policy.makeRequest(baseUrl);
}

生成 downloadUrl 后,服务端下发 downloadUrl 给客户端。客户端收到 downloadUrl 后,和公有资源类似,直接用任意的 HTTP 客户端就可以下载该资源了。唯一需要注意的是,在 downloadUrl 失效却还没有完成下载时,需要重新向服务器申请授权。

无论公有资源还是私有资源,下载过程中客户端并不需要七牛 SDK 参与其中。

断点续下载

无论是公有资源还是私有资源,获得的下载 url 支持标准的 HTTP 断点续传协议。考虑到多数语言都有相应的断点续下载支持的成熟方法,七牛 Nodejs-SDK 并不提供断点续下载相关代码。

资源操作

资源操作限在服务端操作,先进行初始化

qiniu.conf.ACCESS_KEY = '<Your Access Key>';
qiniu.conf.SECRET_KEY = '<Your Secret Key>';

获取文件信息

var client = new qiniu.rs.Client();
client.stat(bucketName, key, function(err, ret) {
  if (!err) {
    // ok 
    // ret has keys (hash, fsize, putTime, mimeType)
  } else {
    console.log(err);
    // http://developer.qiniu.com/docs/v6/api/reference/codes.html
  }
});

删除文件

var client = new qiniu.rs.Client();
client.remove(bucketName, key, function(err, ret) {
  if (!err) {
    // ok
  } else {
    console.log(err);
    // http://developer.qiniu.com/docs/v6/api/reference/codes.html
  }
})

复制/移动文件

var client = new qiniu.rs.Client();
client.copy(bucketSrc, keySrc, bucketDest, keyDest, function(err, ret) {
  if (!err) {
    // ok
  } else {
    console.log(err);
    // http://developer.qiniu.com/docs/v6/api/reference/codes.html
  }
});
var client = new qiniu.rs.Client();
client.move(bucketSrc, keySrc, bucketDest, keyDest, function(err, ret) {
  if (!err) {
    // ok
  } else {
    console.log(err);
    // http://developer.qiniu.com/docs/v6/api/reference/codes.html
  }
});

批量操作

当您需要一次性进行多个操作时, 可以使用批量操作。
批量操作的几个函数中,当返回值errnull的时候,有两种情况:

  1. 批量操作的所有操作都成功
  2. 批量操作仅仅部分成功

因此在返回成功的时候还需要检查各个子项目的返回值(详细见各个示例)。
注意:批量操作不是对单个文件操作的包装,而是有独立的接口。

批量获取文件信息

var path0 = new qiniu.rs.EntryPath(bucketName, key0);
var path1 = new qiniu.rs.EntryPath(bucketName, key1);
var path2 = new qiniu.rs.EntryPath(bucketName, key2);
var client = new qiniu.rs.Client();

client.batchStat([path0, path1, path2], function(err, ret) {
  if (!err) {
    for (i in ret) {
      if (ret[i].code === 200) {
        //ok, ret[i].data has keys (hash, fsize, putTime, mimeType)
      } else {
        // parse error code
        console.log(ret[i].code, ret[i].data);
        // http://developer.qiniu.com/docs/v6/api/reference/codes.html
      }
    }
  } else {
    console.log(err);
    // http://developer.qiniu.com/docs/v6/api/reference/codes.html
  }
});

批量复制文件

var pathSrc0 = new qiniu.rs.EntryPath(bucketName, key0);
var pathDest0 = new qiniu.rs.EntryPath(bucketName, key1);
var pathSrc1 = new qiniu.rs.EntryPath(bucketName, key2);
var pathDest1 = new qiniu.rs.EntryPath(bucketName, key3);

var pair0 = new qiniu.rs.EntryPathPair(pathSrc0, pathDest0);
var pair1 = new qiniu.rs.EntryPathPair(pathSrc1, pathDest1);

var client = new qiniu.rs.Client();

client.batchCopy([pair0, pair1], function(err, ret) {
  if (!err) {
    for (i in ret) {
      if (ret[i].code !== 200) {
        // parse error code
        console.log(ret[i].code, ret[i].data);
        // http://developer.qiniu.com/docs/v6/api/reference/codes.html
      }
    }

  } else {
    console.log(err);
    // http://developer.qiniu.com/docs/v6/api/reference/codes.html
  }
});

批量移动文件

var pathSrc0 = new qiniu.rs.EntryPath(bucketName, key0);
var pathDest0 = new qiniu.rs.EntryPath(bucketName, key1);
var pathSrc1 = new qiniu.rs.EntryPath(bucketName, key2);
var pathDest1 = new qiniu.rs.EntryPath(bucketName, key3);

var pair0 = new qiniu.rs.EntryPathPair(pathSrc0, pathDest0);
var pair1 = new qiniu.rs.EntryPathPair(pathSrc1, pathDest1);

var client = new qiniu.rs.Client();

client.batchMove([pair0, pair1], function(err, ret) {
  if (!err) {
    for (i in ret) {
      if (ret[i] !== 200) {
        // parse error code
        console.log(ret[i].code, ret[i].data);
        // http://developer.qiniu.com/docs/v6/api/reference/codes.html
      }
    }
  } else {
    console.log(err);
    // http://developer.qiniu.com/docs/v6/api/reference/codes.html
  }
});

批量删除文件

var path0 = new qiniu.rs.EntryPath(bucketName, key0);
var path1 = new qiniu.rs.EntryPath(bucketName, key1);
var path2 = new qiniu.rs.EntryPath(bucketName, key2);

var client = new qiniu.rs.Client();

client.batchDelete([path0, path1, path2], function(err, ret) {
  if (!err) {
    for (i in ret) {
      if (ret[i].code !== 200) {
        // parse error code
        console.log(ret[i].code, ret[i].data);
        // http://developer.qiniu.com/docs/v6/api/reference/codes.html
      }
    }
  } else {
    console.log(err);
    // http://developer.qiniu.com/docs/v6/api/reference/codes.html
  }
});

高级管理操作

列出文件

请求某个存储空间(bucket)下的文件列表,如果有前缀,可以按前缀(prefix)进行过滤;第一次调用时置marker为null,之后的调用填上服务器返回的marker(如果有),则列出刚刚为列完的文件

qiniu.conf.ACCESS_KEY = '<Your Access Key>';
qiniu.conf.SECRET_KEY = '<Your Secret Key>';

qiniu.rsf.listPrefix(bucketname, prefix, marker, limit, function(err, ret) {
  if (!err) {
    // process ret.marker & ret.items
  } else {
    console.log(err)
    // http://developer.qiniu.com/docs/v6/api/reference/rs/list.html
  }
});

云处理

查看图像信息

// 生成访问图片的url
var url = qiniu.rs.makeBaseUrl(bucketDomain, key); // bucketDomain为空间域名

// 生成fop_url
var ii = new qiniu.fop.ImageInfo();
url = ii.makeRequest(url);

// 签名,生成private_url。如果是公有bucket则此步可以省略
// 服务端操作使用,或者发送给客户端
var policy = new qiniu.rs.GetPolicy();
url = policy.makeRequest(url);

console.log('在浏览器输入: ' + url);

查看图像Exif

// 生成访问图片的url
var url = qiniu.rs.makeBaseUrl(bucketDomain, key); // bucketDomain为空间域名

// 生成fop_url
var exif = new qiniu.fop.Exif();
url = exif.makeRequest(url);

// 签名,生成private_url。如果是公有bucket则此步可以省略
// 服务端操作使用,或者发送给客户端
var policy = new qiniu.rs.GetPolicy();
url = policy.makeRequest(url);

console.log('在浏览器输入: ' + url);

生成缩略图

// 生成访问图片的url
var url = qiniu.rs.makeBaseUrl(bucketDomain, key);

// 生成fop_url
var iv = new qiniu.fop.ImageView();
iv.width = 100;
url = iv.makeRequest(url);

// 签名,生成private_url。如果是公有bucket则此步可以省略
// 服务端操作使用,或者发送给客户端
var policy = new qiniu.rs.GetPolicy();
url = policy.makeRequest(url);

console.log('在浏览器输入: ' + url);

贡献代码

  • Fork
  • 创建您的特性分支 (git checkout -b my-new-feature)
  • 提交您的改动 (git commit -am ‘Added some feature’)
  • 将您的修改记录提交到远程 git 仓库 (git push origin my-new-feature)
  • 然后到 github 网站的该 git 远程仓库的 my-new-feature 分支下发起 Pull Request

许可证

Copyright © 2014 qiniu.com

基于 MIT 协议发布:

www.opensource.org/licenses/MIT


如果有任何问题,请进入 七牛云存储问答社区 或者发送 工单 咨询